#pragma once

#include "InstanceService.h"
#include "rpc/RPCManager.h"
#include "aoi/AoiHandler.h"
#include "tile/TileHandler.h"
#include "Object/AutoPlayer.h"
#include "Object/Creature.h"
#include "Object/StaticObject.h"
#include "Object/AuraObject.h"
#include "Team/MapTeamManager.h"
#include "IMapHook.h"
#include "GameMap.h"

class MapInstance : public InstanceService, public AsyncTaskOwner,
	public WheelTimerOwner, public WheelRoutineType
{
public:
	MapInstance(const GameMap& gameMap, InstGUID instGuid, ObjGUID ownerGuid);
	~MapInstance();

	void Init();
	void Start();

	void Shutdown();
	void PlayGameOver();

	void Update(uint64 diffTime);

	void PushRecvPacket(const Packet& packet);

	bool IsInited() const;
	bool IsCleaned() const;
	bool IsDeletable() const;

	void DoneOneInitTask() { m_taskWaitInitCnt.fetch_sub(1); }
	int GetTaskWaitInitCnt() const { return m_taskWaitInitCnt.load(); }

	uint32 getMapType() const { return m_instGuid.TID; }
	uint32 getMapId() const { return m_instGuid.MAPID; }
	InstGUID getInstGuid() const { return m_instGuid; }
	ObjGUID getOwnerGuid() const { return m_ownerGuid; }
	uint16 getOwnerGsId() const { return m_ownerGuid.SID; }
	uint32 getOwnerGuidLow() const { return m_ownerGuid.UID; }
	const GameMap& getGameMap() const { return m_gameMap; }
	const MapInfo* getMapInfo() const { return m_gameMap.GetMapInfo(); }

	bool IsShutdown() const { return m_isShutdown; }
	bool IstobeShutdown() const { return m_istobeShutdown; }
	bool IsPlayGameOver() const { return m_isPlayGameOver; }

	lua_State* L;
	WheelTimerMgr sWheelTimerMgr;
	RPCManager sRPCManager;
	AoiHandler sAoiHandler;
	TileHandler sTileHandler;
	CBitMask sCharUpdateInfoBitMask;

private:
	virtual int HandleInstancePacket(const Packet& packet);
	virtual int HandlePlayerPacket(const Packet& packet);

	virtual WheelTimerMgr *GetWheelTimerMgr();

	const GameMap& m_gameMap;
	const InstGUID m_instGuid;
	const ObjGUID m_ownerGuid;

	bool m_isShutdown;
	bool m_istobeShutdown;
	bool m_isPlayGameOver;
	uint64 m_deleteExpireTime;
	std::atomic<int> m_taskWaitInitCnt;

public:
	void SetHook(IMapHook* pHook);
	IMapHook* GetHook() const { return m_pHook; }
	void ChangeFightStage(ArenaFightStage fightStage);
	ArenaFightStage GetFightStage() const { return m_fightStage; }
	bool IsFightingStage() const;
private:
	void Tick();
	void UpdateObjectsValue();
	void FlushPlayersPacket();
	IMapHook* m_pHook;
	ArenaFightStage m_fightStage;
	uint64 m_tickSurplusMS;
	uint64 m_gcSurplusMS;

public:
	void RemoveTeamMember(Player* pPlayer);
	MapTeamManager& GetMapTeamManager() { return m_MapTeamManager; }
private:
	MapTeamManager m_MapTeamManager;

public:
	void ApplyNavPhysicsFlags(uint16 flags, bool isEnable);
	uint16 GetNavPhysicsFlags() const { return m_navPhysicsFlags; }
private:
	uint16 m_navPhysicsFlags;

public:
	size_t GetMostPossiblePlayerCount() const;
	bool IsBackMapInstance() const;
	bool IsSavePlayerPosition() const;

public:
	void ChangeObjectLocation(LocatableObject *pLObj);
	bool IsValidPosition(const vector3f& pos) const;
	vector3f GetNearestValidPosition(const vector3f& pos) const;

public:
	void ApplyAoiTileActorOrder(LocatableObject *pLObj);
	void ApplyAllAoiTileActorOrders();

public:
	void ReloadAoiActorRadiusOrder(AoiActor* pActor);
	void ReloadAoiActorObserverOrder(AoiActor* pActor);
	void ReloadAoiActorSubjectOrder(AoiActor* pActor);
	void ReloadAoiActorStatusOrder(AoiActor* pActor);
	void PushAoiActorOrder(AoiActor* pActor, float x, float z);
	void MoveAoiActorOrder(AoiActor* pActor, float x, float z);
	void PopupAoiActorOrder(AoiActor* pActor, bool deletable);
	void ApplyAoiActorOrder(AoiActor* pActor);
	void ApplyAllAoiActorOrders();
private:
	enum class AoiOrder {
		None, Push, Move, Popup
	};
	enum class AoiOrderFlag {
		ReloadRadius = 1 << 0,
		ReloadObserver = 1 << 1,
		ReloadSubject = 1 << 2,
		ReloadStatus = ReloadObserver | ReloadSubject,
	};
	struct AoiOrderArgs {
		AoiOrder order;
		int flags;
		union {
			struct {
				float x, z;
			};
			bool deletable;
		};
	};
	static const AoiOrderArgs defAoiOrderArgs;
	void ApplyAoiActorOrder(AoiActor* pActor, const AoiOrderArgs& args);
	std::unordered_map<AoiActor*, AoiOrderArgs> m_allAoiActorOrders;

public:
	void PushTileActorOrder(TileActor* pActor, float x, float z);
	void MoveTileActorOrder(TileActor* pActor, float x, float z);
	void PopupTileActorOrder(TileActor* pActor);
	void ApplyTileActorOrder(TileActor* pActor);
	void ApplyAllTileActorOrders();
private:
	enum class TileOrder {
		Push, Move, Popup
	};
	struct TileOrderArgs {
		TileOrder order;
		float x, z;
	};
	static const TileOrderArgs defTileOrderArgs;
	void ApplyTileActorOrder(TileActor* pActor, const TileOrderArgs& args);
	std::unordered_map<TileActor*, TileOrderArgs> m_allTileActorOrders;

public:
	bool ForeachAllPlayer(const LuaFunc& func, uint32 flags = 0) const;
	void BroadcastPacket2AllPlayer(uint32 opcode, const std::string_view& data) const;
	void BroadcastPacket2AllPlayer(const INetPacket& pck) const;
	void SendSysMsgToAll(ChannelType channelType, uint32 msgFlags,
		uint32 msgId, const std::string_view& msgArgs = emptyStringView) const;

public:
	bool TryPlayerEnterMap(ObjGUID playerGuid,
		const PlayerTeleportInfo& tpInfo, INetStream& pck, int32 err = RPCErrorNone);
	void SendPlayerEnterMapResp(bool isSucc, ObjGUID playerGuid);
public:
	void ForcePlayerLeave(Player* pPlayer);
	void LogoutPlayer(ObjGUID playerGuid);
	void TryPlayerLeaveMap(bool isRespDone, ObjGUID playerGuid,
		InstGUID instGuid = InstGUID_NULL, const vector3f1f& tgtPos = vector3f1f_NULL);
	void TeleportPlayer(Player* pPlayer, InstGUID instGuid, const vector3f1f& tgtPos,
		TeleportType tpType, uint32 tpFlags, const std::string_view& initArgs);
private:
	void OnPlayerLeaveMap(Player* pPlayer);
	void SendPlayerLeaveMapResp(bool isSucc, ObjGUID playerGuid,
		InstGUID instGuid = InstGUID_NULL, const vector3f1f& tgtPos = vector3f1f_NULL);

public:
	void EventOfflinePlayer(size_t gsIdx = -1);
	void EventOfflineGatePlayer(void* pGateSession);
	void EventAddPendingEnterPlayer(
		uint32 gsIdx, const std::shared_ptr<CharTeleportInfo>& tpInfoPtr);
	void EventPushStrayPlayer(
		Player* pPlayer, const vector3f1f& tgtPos, uint32 tpFlags);
	bool PushObject(LocatableObject* pLObj);
	bool RemoveObject(LocatableObject* pLObj);
	bool AddPendingEnterPlayer(ObjGUID guid);
	bool RemovePendingEnterPlayer(ObjGUID guid, bool isLeave = false);
	bool AddStrayPlayer(Player* pPlayer);
	bool RemoveStrayPlayer(Player* pPlayer, bool isLeave = false);
	void AddPendingDeleteObject(LocatableObject* pLObj);
	size_t GetPendingEnterPlayerCount() const { return m_pendingEnterPlayers.size(); }
private:
	void SendPlayerTeleportBeginEnterInstanceResult(uint32 gsIdx,
		GErrorCode errCode, uint64 playerGuid, const vector3f1f& tgtPos = vector3f1f_NULL);
	std::unordered_set<ObjGUID> m_pendingEnterPlayers;
	std::unordered_set<LocatableObject*> m_pendingDeleteObjects;

public:
	Creature* DeployCreature(
		uint32 entry, float x, float y, float z, float o,
		uint32 level, uint32 idleType, uint32 lifeTime);
	StaticObject* DeployStaticObject(
		uint32 entry, float x, float y, float z, float o,
		float radius, uint32 lifeTime);
	void DeployStageCreature(LuaFunc func, uint32 entry, size_t n,
		float x, float y, float z, float o, float range,
		uint32 level, uint32 idleType, uint32 lifeTime);
	void DeployStageStaticObject(LuaFunc func, uint32 entry, size_t n,
		float x, float y, float z, float o, float range,
		float radius, uint32 lifeTime);
	void CleanupStageCreature(uint32 entry);
	void CleanupStageStaticObject(uint32 entry);
	void ClearAllHostileForcesCreature();

public:
	Creature* SpawnCreature(const CreatureSpawn* pSpawn);
	Creature* CreateCustomCreature(
		uint32 entry, const vector3f1f& pos, const CreatureSpawnArgs* args = NULL);
	StaticObject* SpawnStaticObject(const StaticObjectSpawn* pSpawn);
	StaticObject* CreateCustomStaticObject(
		uint32 entry, const vector3f1f& pos, const SObjSpawnArgs* args = NULL);
	AuraObject* SpawnAuraObject(const AuraPrototype* pProto,
		Spell* pSpell, uint32 effectIndex, Unit* pOwner);
private:
	uint32 m_creatureSeed;
	uint32 m_staticObjectSeed;
	uint32 m_auraObjectSeed;

public:
	void LoadAllSpawnObjects();
private:
	void RemoveSpawnObject(AoiActor* actor);
	class CreatureSpawnObject;
	class StaticObjectSpawnObject;
	std::unordered_set<AoiActor*> m_allSpawnObject;

public:
	AutoPlayer* GetAutoPlayer(ObjGUID guid) const;
	Player* GetAvailablePlayer(ObjGUID guid) const;
	Player* GetStrayPlayer(ObjGUID guid) const;
	Player* GetPlayer(ObjGUID guid) const;
	Creature* GetCreature(ObjGUID guid) const;
	Creature* GetCreatureByEntry(uint32 entry) const;
	StaticObject* GetStaticObject(ObjGUID guid) const;
	StaticObject* GetStaticObjectByEntry(uint32 entry) const;
	AuraObject* GetAuraObject(ObjGUID guid) const;
	Unit* GetUnit(ObjGUID guid) const;
	LocatableObject* GetLocatableObject(ObjGUID guid) const;
	bool ForeachPlayer(const LuaFunc& func) const;
	bool ForeachCreature(const LuaFunc& func) const;
	bool ForeachStaticObject(const LuaFunc& func) const;
	bool ForeachAuraObject(const LuaFunc& func) const;
	const std::unordered_map<ObjGUID, AutoPlayer*>& GetAutoPlayerStorageMap() const;
	const std::unordered_map<ObjGUID, Player*>& GetStrayPlayerStorageMap() const;
	const std::unordered_map<ObjGUID, Player*>& GetPlayerStorageMap() const;
	const std::unordered_map<ObjGUID, Creature*>& GetCreatureStorageMap() const;
	const std::unordered_map<ObjGUID, StaticObject*>& GetStaticObjectStorageMap() const;
	const std::unordered_map<ObjGUID, AuraObject*>& GetAuraObjectStorageMap() const;
	size_t GetStrayPlayerCount() const { return m_StrayPlayerStorageMap.size(); }
	size_t GetPlayerCount() const { return m_PlayerStorageMap.size(); }
private:
	std::unordered_map<ObjGUID, AutoPlayer*> m_AutoPlayerStorageMap;
	std::unordered_map<ObjGUID, Player*> m_StrayPlayerStorageMap;
	std::unordered_map<ObjGUID, Player*> m_PlayerStorageMap;
	std::unordered_map<ObjGUID, Creature*> m_CreatureStorageMap;
	std::unordered_map<ObjGUID, StaticObject*> m_StaticObjectStorageMap;
	std::unordered_map<ObjGUID, AuraObject*> m_AuraObjectStorageMap;

public:
	void MapHookEvent_OnPlayerPendingEnter(ObjGUID playerGuid);
	void MapHookEvent_OnPlayerLeaveMap(ObjGUID playerGuid);
	void MapHookEvent_OnPlayerEnter(Player* pPlayer);
	void MapHookEvent_OnPlayerLeave(Player* pPlayer);
	void MapHookEvent_OnCreatureSpawn(Creature* pCreature);
	void MapHookEvent_OnStaticObjectSpawn(StaticObject* pSObj);
	void MapHookEvent_OnUnitHurted(Unit* pUnit, Unit* pHurter, uint64 hurtValue);
	void MapHookEvent_OnUnitKilled(Unit* pUnit, Unit* pKiller);
	void MapHookEvent_OnUnitDead(Unit* pUnit, Unit* pKiller);
	void MapHookEvent_OnSendMessage(const char* funcName, const LuaTable& args);
public:
	uint32 AttachMapHookInfo(LuaTable&& t);
	void DetachMapHookInfo(uint32 key);
private:
	void DestructAllMapHookInfos();
	void CleanMapHookInfos();
	uint32 NewMapHookKey() { return ++m_mapHookUniqueKey; }
	uint32 m_mapHookUniqueKey;
	std::map<uint32, MapHookInfo*> m_mapHookInfos;
	std::vector<uint32> m_gcMapHookKeys;
};

inline const std::unordered_map<ObjGUID, AutoPlayer*>& MapInstance::GetAutoPlayerStorageMap() const {
	return m_AutoPlayerStorageMap;
}
inline const std::unordered_map<ObjGUID, Player*>& MapInstance::GetStrayPlayerStorageMap() const {
	return m_StrayPlayerStorageMap;
}
inline const std::unordered_map<ObjGUID, Player*>& MapInstance::GetPlayerStorageMap() const {
	return m_PlayerStorageMap;
}
inline const std::unordered_map<ObjGUID, Creature*>& MapInstance::GetCreatureStorageMap() const {
	return m_CreatureStorageMap;
}
inline const std::unordered_map<ObjGUID, StaticObject*>& MapInstance::GetStaticObjectStorageMap() const {
	return m_StaticObjectStorageMap;
}
inline const std::unordered_map<ObjGUID, AuraObject*>& MapInstance::GetAuraObjectStorageMap() const {
	return m_AuraObjectStorageMap;
}

inline bool MapInstance::IsFightingStage() const { return m_fightStage == ArenaFightStage::eFighting; }
